package bma.common.netty.tester;

import java.net.InetSocketAddress;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandler;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;

import bma.common.langutil.io.HostPort;
import bma.common.langutil.log.LogPrinter;
import bma.common.langutil.log.LogPrinter.LEVEL;
import bma.common.langutil.pipeline.CommonPipelineBuilder;
import bma.common.netty.NettyChannelPipeline;
import bma.common.netty.handler.ChannelHandlerLog;
import bma.common.netty.handler.ChannelHandlerLog.TYPE;
import bma.common.netty.proxy.ProxyHandler;

public class NettyProxyTester {

	final org.slf4j.Logger log = org.slf4j.LoggerFactory
			.getLogger(NettyProxyTester.class);

	private HostPort remote;
	private CommonPipelineBuilder<ChannelHandler> serverBuilder;
	private CommonPipelineBuilder<ChannelHandler> remoteBuilder;

	public HostPort getRemote() {
		return remote;
	}

	public void setRemote(HostPort remote) {
		this.remote = remote;
	}

	public CommonPipelineBuilder<ChannelHandler> getServerBuilder() {
		return serverBuilder;
	}

	public void setServerBuilder(
			CommonPipelineBuilder<ChannelHandler> serverBuilder) {
		this.serverBuilder = serverBuilder;
	}

	public CommonPipelineBuilder<ChannelHandler> getRemoteBuilder() {
		return remoteBuilder;
	}

	public void setRemoteBuilder(
			CommonPipelineBuilder<ChannelHandler> remoteBuilder) {
		this.remoteBuilder = remoteBuilder;
	}

	public boolean proxyTo(Channel channel, Object v, boolean isClient2remote)
			throws Exception {
		return false;
	}

	protected ChannelPipelineFactory serverFactory() {
		return new ChannelPipelineFactory() {

			@Override
			public ChannelPipeline getPipeline() throws Exception {
				ChannelPipeline p = Channels.pipeline();
				p.addLast("proxy", new ProxyHandler() {

					@Override
					protected void proxyTo(Channel channel, Object v,
							boolean isClient2remote) throws Exception {
						if (!NettyProxyTester.this.proxyTo(channel, v,
								isClient2remote)) {
							super.proxyTo(channel, v, isClient2remote);
						}
					}

					@Override
					protected ClientBootstrap getRemoteBootstrap(
							ChannelHandlerContext ctx, ChannelStateEvent e,
							ChannelHandler handler) throws Exception {
						if (remote == null)
							throw new NullPointerException("remote");
						Executor executor = Executors.newCachedThreadPool();
						ClientBootstrap cb = new ClientBootstrap(
								new NioClientSocketChannelFactory(executor,
										executor));
						cb.setPipelineFactory(remoteFactory(handler));
						cb.setOption("remoteAddress",
								remote.createInetSocketAddress());
						return cb;
					}

					@Override
					protected String proxyTitle() {
						return remote == null ? super.toString() : "Proxy["
								+ remote + "]";
					}
				});

				if (serverBuilder != null) {
					serverBuilder.buildPipeline(new NettyChannelPipeline(p));
				}
				p.addFirst("log", new ChannelHandlerLog(new LogPrinter(log,
						LEVEL.DEBUG), TYPE.ALL, 1024 * 1000));
				return p;
			}
		};
	}

	private class MyChannelPipelineFactory implements ChannelPipelineFactory {

		private ChannelHandler handler;

		public MyChannelPipelineFactory(ChannelHandler handler) {
			super();
			this.handler = handler;
		}

		@Override
		public ChannelPipeline getPipeline() throws Exception {
			ChannelPipeline p = Channels.pipeline();
			p.addLast("proxy", handler);
			if (remoteBuilder != null) {
				remoteBuilder.buildPipeline(new NettyChannelPipeline(p));
			}
			p.addFirst("log", new ChannelHandlerLog(new LogPrinter(log,
					LEVEL.DEBUG), TYPE.ALL, 1024 * 1000));
			return p;
		}
	}

	protected ChannelPipelineFactory remoteFactory(ChannelHandler handler) {
		return new MyChannelPipelineFactory(handler);
	}

	private Channel serverChannel;

	public Channel start(int localPort, HostPort remote, long time)
			throws Exception {

		this.remote = remote;
		System.err
				.println("start ProxyTester *:" + localPort + " >> " + remote);

		// Configure the bootstrap.
		Executor executor = Executors.newCachedThreadPool();

		ServerBootstrap sb = new ServerBootstrap(
				new NioServerSocketChannelFactory(executor, executor));
		sb.setPipelineFactory(serverFactory());
		serverChannel = sb.bind(new InetSocketAddress(localPort));

		return serverChannel;
	}

	public void close() {
		if (serverChannel != null) {
			serverChannel.close();
		}
	}
}
